Aller au contenu principal

Interfaces

Les interfaces sont un concept fondamental de la programmation orientée objet en Java. Elles permettent de définir des contrats que les classes doivent respecter, favorisant ainsi la flexibilité et l'extensibilité du code.

Qu'est-ce qu'une interface ?

Un contrat

Une interface peut être vue comme un contrat. Elle définit un ensemble de méthodes (des "signatures" de fonctions) qu'une classe doit implémenter si elle "accepte" ce contrat.

En d'autres termes, une interface dit : "Toute classe qui implémente cette interface doit fournir une implémentation pour ces méthodes".

Une abstraction

Les interfaces permettent de définir un comportement sans se soucier de la manière dont ce comportement est implémenté.

Cela favorise l'abstraction, car on peut travailler avec des objets en se basant sur leur comportement (défini par l'interface) plutôt que sur leur type concret.

Multiples implémentations

Plusieurs classes peuvent implémenter la même interface, chacune fournissant sa propre implémentation des méthodes.

Cela permet de créer des systèmes flexibles et extensibles, où différents objets peuvent être utilisés de manière interchangeable tant qu'ils respectent le même contrat.

À quoi servent les interfaces ?

Définir des comportements communs

Les interfaces permettent de définir des comportements communs à plusieurs classes, même si ces classes n'ont pas de lien de parenté.

Exemple : Un Chien et une Voiture n'ont aucun lien d'héritage, mais peuvent tous deux implémenter Sonore.

Favoriser le polymorphisme

Les interfaces sont essentielles pour le polymorphisme, qui permet de traiter des objets de différentes classes de manière uniforme.

On peut manipuler des objets par leur interface plutôt que par leur classe concrète.

Découpler les composants

Les interfaces permettent de découpler les composants d'un système, ce qui facilite la maintenance et les tests.

Le code dépend d'une abstraction (l'interface) plutôt que d'une implémentation concrète.

Syntaxe de déclaration d'une interface

Une interface se déclare avec le mot-clé interface :

public interface NomInterface {
// Constantes (public static final par défaut)
int CONSTANTE = 100;

// Méthodes abstraites (public abstract par défaut)
void methode1();
String methode2(int param);
boolean methode3(String param1, double param2);
}

Important :

  • Toutes les méthodes sont publiques et abstraites par défaut
  • Toutes les variables sont des constantes (public static final)
  • Une interface ne peut pas être instanciée directement
  • Une interface n'a pas de constructeur

Implémentation d'une interface

Une classe implémente une interface avec le mot-clé implements :

public class MaClasse implements MonInterface {
// Doit implémenter TOUTES les méthodes de l'interface
@Override
public void methode1() {
// Implémentation
}

@Override
public String methode2(int param) {
// Implémentation
return "résultat";
}

@Override
public boolean methode3(String param1, double param2) {
// Implémentation
return true;
}
}

Règles importantes :

  • Une classe doit implémenter toutes les méthodes de l'interface
  • L'annotation @Override est fortement recommandée
  • Les méthodes doivent être déclarées public

Exemple simple : Interface Sonore

Imaginez une interface Sonore qui définit une méthode faireDuBruit().

  • Une classe Chien pourrait implémenter Sonore et afficher "Wouf !"
  • Une classe Chat pourrait implémenter Sonore et afficher "Miaou !"
  • Une classe Voiture pourrait aussi implémenter Sonore et afficher "Vroum !"

Ainsi, on peut traiter un Chien, un Chat et une Voiture de manière uniforme en tant qu'objets Sonore, sachant qu'ils peuvent tous faireDuBruit().

// Interface générique pour les objets qui produisent un son
interface Sonore {
void faireDuBruit();
}

// Classe Chien qui implémente l'interface Sonore
class Chien implements Sonore {
@Override
public void faireDuBruit() {
System.out.println("Wouf !");
}
}

// Classe Chat qui implémente l'interface Sonore
class Chat implements Sonore {
@Override
public void faireDuBruit() {
System.out.println("Miaou !");
}
}

// Classe Voiture qui implémente l'interface Sonore
class Voiture implements Sonore {
@Override
public void faireDuBruit() {
System.out.println("Vroum !");
}
}

public class Main {
public static void main(String[] args) {
// Déclaration et instanciation des objets
Chien monChien = new Chien();
Chat monChat = new Chat();
Voiture maVoiture = new Voiture();
Sonore objetSonore = new Chien(); // Polymorphisme

// Appel de la méthode faireDuBruit() sur chaque objet
monChien.faireDuBruit();
monChat.faireDuBruit();
maVoiture.faireDuBruit();
objetSonore.faireDuBruit();

// Et en utilisant un tableau:

// Création d'un tableau d'objets Sonore
Sonore[] tableauSonores = new Sonore[3];
tableauSonores[0] = new Chien();
tableauSonores[1] = new Chat();
tableauSonores[2] = new Voiture();

// Parcours du tableau et appel de faireDuBruit()
for (Sonore objet : tableauSonores) {
objet.faireDuBruit();
}
}
}

Implémentation multiple

Contrairement à l'héritage (qui est limité à une seule classe parent), une classe peut implémenter plusieurs interfaces en même temps.

Syntaxe pour implémenter plusieurs interfaces

public class MaClasse implements Interface1, Interface2, Interface3 {
// Doit implémenter toutes les méthodes de toutes les interfaces
}

Exemple pratique

// Première interface
interface Volant {
void voler();
int getAltitude();
}

// Deuxième interface
interface Nageant {
void nager();
int getProfondeur();
}

// Classe qui implémente les deux interfaces
class Canard implements Volant, Nageant {
private int altitude;
private int profondeur;

@Override
public void voler() {
altitude = 100;
System.out.println("Le canard vole à " + altitude + "m");
}

@Override
public int getAltitude() {
return altitude;
}

@Override
public void nager() {
profondeur = 2;
System.out.println("Le canard nage à " + profondeur + "m de profondeur");
}

@Override
public int getProfondeur() {
return profondeur;
}
}

// Autre classe qui n'implémente qu'une seule interface
class Avion implements Volant {
private int altitude;

@Override
public void voler() {
altitude = 10000;
System.out.println("L'avion vole à " + altitude + "m");
}

@Override
public int getAltitude() {
return altitude;
}
}

public class Main {
public static void main(String[] args) {
Canard canard = new Canard();
canard.voler(); // Le canard peut voler
canard.nager(); // Et aussi nager

// Polymorphisme avec les interfaces
Volant volant1 = new Canard();
Volant volant2 = new Avion();

volant1.voler();
volant2.voler();

// Tableau polymorphe
Volant[] objetsVolants = new Volant[2];
objetsVolants[0] = new Canard();
objetsVolants[1] = new Avion();

for (Volant v : objetsVolants) {
v.voler();
}
}
}

Avantage de l'implémentation multiple : Une classe peut combiner plusieurs comportements sans être limitée par la hiérarchie d'héritage.

Méthodes par défaut (Java 8+)

Depuis Java 8, les interfaces peuvent contenir des méthodes avec implémentation grâce au mot-clé default.

Pourquoi les méthodes par défaut ?

  • Permet d'ajouter de nouvelles méthodes à une interface sans casser le code existant
  • Les classes qui implémentent l'interface peuvent utiliser l'implémentation par défaut ou la redéfinir

Syntaxe

interface Vehicule {
// Méthode abstraite classique
void demarrer();
void arreter();

// Méthode par défaut (avec implémentation)
default void klaxonner() {
System.out.println("Beep beep!");
}

default void afficherInfo() {
System.out.println("Je suis un véhicule");
}
}

class Voiture implements Vehicule {
@Override
public void demarrer() {
System.out.println("La voiture démarre");
}

@Override
public void arreter() {
System.out.println("La voiture s'arrête");
}

// Peut utiliser klaxonner() sans la redéfinir
// Ou peut la redéfinir si nécessaire
@Override
public void klaxonner() {
System.out.println("HONK HONK!");
}
}

public class Main {
public static void main(String[] args) {
Voiture voiture = new Voiture();
voiture.demarrer(); // La voiture démarre
voiture.klaxonner(); // HONK HONK! (version redéfinie)
voiture.afficherInfo(); // Je suis un véhicule (version par défaut)
}
}

Interfaces vs Classes Abstraites : Tableau comparatif

CaractéristiqueClasses AbstraitesInterfaces
ObjectifPartager du code commun et définir un comportement de baseDéfinir un contrat, des capacités
Relation"Est-un" (hiérarchie)"Peut-faire" (capacités)
Mot-cléextendsimplements
Héritage multipleNon (une seule classe parent)Oui (plusieurs interfaces)
MéthodesAbstraites ET concrètesAbstraites et default
AttributsTous types (public, private, protected)Seulement constantes (public static final)
ConstructeursOui (appelés par les sous-classes)Non
Modificateurs d'accèsTous (public, private, protected)Public uniquement
InstanciationNon (abstraite)Non

Quand utiliser une interface ?

Utilisez une interface quand :

  • Vous voulez définir un contrat (comportement) sans imposer d'implémentation
  • Vous avez besoin d'implémentation multiple (plusieurs interfaces)
  • Les classes qui l'implémentent n'ont pas de relation hiérarchique
  • Vous voulez définir des capacités ("peut voler", "peut nager", "peut faire du bruit")
  • Plusieurs classes non liées doivent partager un comportement commun

Quand utiliser une classe abstraite ?

Utilisez une classe abstraite quand :

  • Vous voulez partager du code entre classes liées
  • Vous avez une relation "est-un" claire (Chien est un Animal)
  • Vous avez besoin d'attributs non-constants
  • Vous voulez définir des constructeurs
  • Vous avez un comportement commun à plusieurs classes

Bonnes pratiques

  1. Nommage des interfaces

    • Utilisez des adjectifs : Comparable, Runnable, Serializable, Cloneable
    • Ou des noms de capacité : Volant, Nageant, Sonore
  2. Interfaces cohésives

    • Une interface doit représenter un seul concept
    • Favorisez les interfaces petites et ciblées
    • Exemple : Une interface Volant plutôt qu'une interface Animal trop générale
  3. Documentation

    • Documentez le contrat que l'interface impose
    • Précisez les comportements attendus de chaque méthode
    • Indiquez les valeurs de retour et les exceptions possibles
  4. Préférez les interfaces aux classes abstraites

    • Plus flexible (implémentation multiple)
    • Meilleur découplage du code
    • Facilite les tests et la maintenance
  5. Utilisez @Override

    • Toujours annoter les méthodes implémentées
    • Aide à détecter les erreurs de signature

Résumé

Points clés à retenir

  1. Interface = Contrat : Définit ce qu'une classe doit faire, pas comment

  2. Implémentation multiple : Une classe peut implémenter plusieurs interfaces (contrairement à l'héritage de classe)

  3. Polymorphisme : Les interfaces permettent un polymorphisme flexible et découplé

  4. Méthodes default : Permettent d'ajouter des méthodes avec implémentation sans casser le code existant

  5. Relation "peut-faire" : Une interface définit une capacité, pas une hiérarchie

  6. Constantes : Toutes les variables d'une interface sont public static final

  7. Méthodes abstraites : Toutes les méthodes sont public abstract par défaut

  8. Interfaces standards : Java fournit de nombreuses interfaces utiles (Comparable, Serializable, Cloneable, etc.)

Exemple récapitulatif

// Interface définissant une capacité
interface Rechargeable {
void recharger();
int getNiveauBatterie();
}

// Plusieurs classes non liées peuvent implémenter la même interface
class Telephone implements Rechargeable {
private int batterie = 50;

@Override
public void recharger() {
batterie = 100;
System.out.println("Téléphone rechargé");
}

@Override
public int getNiveauBatterie() {
return batterie;
}
}

class Voiture implements Rechargeable {
private int batterie = 30;

@Override
public void recharger() {
batterie = 100;
System.out.println("Voiture rechargée");
}

@Override
public int getNiveauBatterie() {
return batterie;
}
}

// Polymorphisme : utiliser l'interface comme type
public class Main {
public static void main(String[] args) {
Rechargeable[] appareils = new Rechargeable[2];
appareils[0] = new Telephone();
appareils[1] = new Voiture();

// Même code pour tous les appareils rechargeables
for (Rechargeable appareil : appareils) {
System.out.println("Niveau: " + appareil.getNiveauBatterie() + "%");
appareil.recharger();
System.out.println("Niveau: " + appareil.getNiveauBatterie() + "%");
}
}
}

Les interfaces sont un outil puissant pour créer du code flexible, maintenable et extensible en Java !